﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic;
using System.Linq;
using System.Reflection;

namespace Fwql.Net.Extensions
{
    public static class TypeExtension
    {
        /// <summary>
        /// 预定义类型数组
        /// </summary>
        public static readonly Type[] PredefinedTypes = {
                                                            typeof (Object), typeof (Boolean), typeof (Char),
                                                            typeof (String), typeof (SByte), typeof (Byte),
                                                            typeof (Int16), typeof (UInt16), typeof (Int32),
                                                            typeof (UInt32), typeof (Int64), typeof (UInt64),
                                                            typeof (Single), typeof (Double), typeof (Decimal),
                                                            typeof (DateTime), typeof (TimeSpan), typeof (Guid),
                                                            typeof (Math), typeof (Convert)
                                                        };

        /// <summary>
        /// 	Creates and returns an instance of the desired type
        /// </summary>
        /// <param name = "type">The type to be instanciated.</param>
        /// <param name = "constructorParameters">Optional constructor parameters</param>
        /// <returns>The instanciated object</returns>
        /// <example>
        /// 	<code>
        /// 		var type = Type.GetType(".NET full qualified class Type")
        /// 		var instance = type.CreateInstance();
        /// 	</code>
        /// </example>
        public static object CreateInstance(this Type type, params object[] constructorParameters)
        {
            var instance = Activator.CreateInstance(type, constructorParameters);
            return instance;
        }

        /// <summary>
        /// 	Creates and returns an instance of the desired type casted to the generic parameter type T
        /// </summary>
        /// <typeparam name = "T">The data type the instance is casted to.</typeparam>
        /// <param name = "type">The type to be instanciated.</param>
        /// <param name = "constructorParameters">Optional constructor parameters</param>
        /// <returns>The instanciated object</returns>
        /// <example>
        /// 	<code>
        /// 		var type = Type.GetType(".NET full qualified class Type")
        /// 		var instance = type.CreateInstance&lt;IDataType&gt;();
        /// 	</code>
        /// </example>
        public static T CreateInstance<T>(this Type type, params object[] constructorParameters)
        {
            var instance = Activator.CreateInstance(type, constructorParameters);
            return (T)instance;
        }

        /// <summary>
        /// Closes the passed generic type with the provided type arguments and returns an instance of the newly constructed type.
        /// </summary>
        /// <typeparam name="T">The typed type to be returned.</typeparam>
        /// <param name="genericType">The open generic type.</param>
        /// <param name="typeArguments">The type arguments to close the generic type.</param>
        /// <returns>An instance of the constructed type casted to T.</returns>
        public static T CreateGenericTypeInstance<T>(this Type genericType, params Type[] typeArguments) where T : class
        {
            var constructedType = genericType.MakeGenericType(typeArguments);
            var instance = Activator.CreateInstance(constructedType);
            return (instance as T);
        }

        /// <summary>
        /// 判断是否为预定义类型
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsPredefinedType(this Type type)
        {
            return PredefinedTypes.Any(t => t == type);
        }

        /// <summary>
        /// 返回类型中第一个可以用来排序的属性名称
        /// </summary>
        /// <param name="type">类型</param>
        /// <returns></returns>
        public static string FirstSortableProperty(this Type type)
        {
            PropertyInfo firstSortableProperty =
                type.GetProperties().Where(property => property.PropertyType.IsPredefinedType()).FirstOrDefault();

            return firstSortableProperty == null ? string.Empty : firstSortableProperty.Name;
        }

        /// <summary>
        /// 判断类型是否是可空类型
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsNullableType(this Type type)
        {
            return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
        }

        /// <summary>
        /// 返回指定类型的非可空类型
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static Type GetNonNullableType(this Type type)
        {
            return IsNullableType(type) ? type.GetGenericArguments()[0] : type;
        }

        /// <summary>
        /// 返回类型名称
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static string GetTypeName(this Type type)
        {
            Type baseType = GetNonNullableType(type);
            string s = baseType.Name;
            if (type != baseType)
                s += '?';
            return s;
        }

        /// <summary>
        /// 判断类型是否为枚举类型
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsEnumType(this Type type)
        {
            return GetNonNullableType(type).IsEnum;
        }

        /// <summary>
        /// 判断源类型是否兼容目标类型
        /// </summary>
        /// <param name="source">源</param>
        /// <param name="target">目标</param>
        /// <returns></returns>
        [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
        public static bool IsCompatibleWith(this Type source, Type target)
        {
            if (source == target)
                return true;
            if (!target.IsValueType)
                return target.IsAssignableFrom(source);
            Type st = source.GetNonNullableType();
            Type tt = target.GetNonNullableType();
            if (st != source && tt == target)
                return false;
            TypeCode sc = st.IsEnum ? TypeCode.Object : Type.GetTypeCode(st);
            TypeCode tc = tt.IsEnum ? TypeCode.Object : Type.GetTypeCode(tt);
            switch (sc)
            {
                case TypeCode.SByte:
                    switch (tc)
                    {
                        case TypeCode.SByte:
                        case TypeCode.Int16:
                        case TypeCode.Int32:
                        case TypeCode.Int64:
                        case TypeCode.Single:
                        case TypeCode.Double:
                        case TypeCode.Decimal:
                            return true;
                    }
                    break;
                case TypeCode.Byte:
                    switch (tc)
                    {
                        case TypeCode.Byte:
                        case TypeCode.Int16:
                        case TypeCode.UInt16:
                        case TypeCode.Int32:
                        case TypeCode.UInt32:
                        case TypeCode.Int64:
                        case TypeCode.UInt64:
                        case TypeCode.Single:
                        case TypeCode.Double:
                        case TypeCode.Decimal:
                            return true;
                    }
                    break;
                case TypeCode.Int16:
                    switch (tc)
                    {
                        case TypeCode.Int16:
                        case TypeCode.Int32:
                        case TypeCode.Int64:
                        case TypeCode.Single:
                        case TypeCode.Double:
                        case TypeCode.Decimal:
                            return true;
                    }
                    break;
                case TypeCode.UInt16:
                    switch (tc)
                    {
                        case TypeCode.UInt16:
                        case TypeCode.Int32:
                        case TypeCode.UInt32:
                        case TypeCode.Int64:
                        case TypeCode.UInt64:
                        case TypeCode.Single:
                        case TypeCode.Double:
                        case TypeCode.Decimal:
                            return true;
                    }
                    break;
                case TypeCode.Int32:
                    switch (tc)
                    {
                        case TypeCode.Int32:
                        case TypeCode.Int64:
                        case TypeCode.Single:
                        case TypeCode.Double:
                        case TypeCode.Decimal:
                            return true;
                    }
                    break;
                case TypeCode.UInt32:
                    switch (tc)
                    {
                        case TypeCode.UInt32:
                        case TypeCode.Int64:
                        case TypeCode.UInt64:
                        case TypeCode.Single:
                        case TypeCode.Double:
                        case TypeCode.Decimal:
                            return true;
                    }
                    break;
                case TypeCode.Int64:
                    switch (tc)
                    {
                        case TypeCode.Int64:
                        case TypeCode.Single:
                        case TypeCode.Double:
                        case TypeCode.Decimal:
                            return true;
                    }
                    break;
                case TypeCode.UInt64:
                    switch (tc)
                    {
                        case TypeCode.UInt64:
                        case TypeCode.Single:
                        case TypeCode.Double:
                        case TypeCode.Decimal:
                            return true;
                    }
                    break;
                case TypeCode.Single:
                    switch (tc)
                    {
                        case TypeCode.Single:
                        case TypeCode.Double:
                            return true;
                    }
                    break;
                default:
                    if (st == tt)
                        return true;
                    break;
            }
            return false;
        }

        /// <summary>
        /// 返回指定类型的泛型类型
        /// </summary>
        /// <param name="type"></param>
        /// <param name="genericType"></param>
        /// <returns></returns>
        public static Type FindGenericType(this Type type, Type genericType)
        {
            while (type != null && type != typeof(object))
            {
                if (type.IsGenericType && type.GetGenericTypeDefinition() == genericType)
                    return type;
                if (genericType.IsInterface)
                {
                    foreach (Type intfType in type.GetInterfaces())
                    {
                        Type found = intfType.FindGenericType(genericType);
                        if (found != null)
                            return found;
                    }
                }
                type = type.BaseType;
            }
            return null;
        }

        /// <summary>
        /// 返回类型名称，过滤掉命名空间
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static string GetName(this Type type)
        {
            return type.FullName.Replace(type.Namespace + ".", string.Empty);
        }

        /// <summary>
        /// 返回类型默认值
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static object DefaultValue(this Type type)
        {
            if (type.IsValueType)
                return Activator.CreateInstance(type);
            return null;
        }

        /// <summary>
        /// 返回类型的指定属性或字段
        /// </summary>
        /// <param name="type"></param>
        /// <param name="memberName">属性名或字段名</param>
        /// <returns></returns>
        public static MemberInfo FindPropertyOrField(this Type type, string memberName)
        {
            MemberInfo memberInfo = type.FindPropertyOrField(memberName, false) ??
                                    type.FindPropertyOrField(memberName, true);

            return memberInfo;
        }

        /// <summary>
        /// 返回类型的指定属性或字段
        /// </summary>
        /// <param name="type"></param>
        /// <param name="memberName">属性名或字段名</param>
        /// <param name="staticAccess">true检索静态属性或字段，false检索实例属性或字段。</param>
        /// <returns></returns>
        public static MemberInfo FindPropertyOrField(this Type type, string memberName, bool staticAccess)
        {
            BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly |
                                 (staticAccess ? BindingFlags.Static : BindingFlags.Instance);
            return (from t in type.SelfAndBaseTypes() select t.FindMembers(MemberTypes.Property | MemberTypes.Field, flags, Type.FilterNameIgnoreCase, memberName) into members where members.Length != 0 select members[0]).FirstOrDefault();
        }

        /// <summary>
        /// 返回类型本身和其所有父类型(包括接口)的集合
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static IEnumerable<Type> SelfAndBaseTypes(this Type type)
        {
            if (type.IsInterface)
            {
                List<Type> types = new List<Type>();
                AddInterface(types, type);
                return types;
            }
            return SelfAndBaseClasses(type);
        }

        /// <summary>
        /// 返回类型本身和其所有父类(不包括接口)的集合
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static IEnumerable<Type> SelfAndBaseClasses(this Type type)
        {
            while (type != null)
            {
                yield return type;
                type = type.BaseType;
            }
        }

        private static void AddInterface(List<Type> types, Type type)
        {
            if (!types.Contains(type))
            {
                types.Add(type);
                foreach (Type t in type.GetInterfaces())
                    AddInterface(types, t);
            }
        }

        /// <summary>
        /// 判断是否为动态类型
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsDynamicObject(this Type type)
        {
            return type == typeof(object) || type.IsCompatibleWith(typeof(IDynamicMetaObjectProvider));
        }

        #region GetNumericType

        /// <summary>
        /// 判断是否是数值类型
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsNumericType(this Type type)
        {
            return GetNumericTypeKind(type) != 0;
        }

        /// <summary>
        /// 判断是否为有符号整数
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsSignedIntegralType(this Type type)
        {
            return GetNumericTypeKind(type) == 2;
        }

        /// <summary>
        /// 判断是否为无符号整数
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsUnsignedIntegralType(this Type type)
        {
            return GetNumericTypeKind(type) == 3;
        }

        private static int GetNumericTypeKind(this Type type)
        {
            if (type == null)
            {
                return 0;
            }

            type = GetNonNullableType(type);

            if (type.IsEnum)
            {
                return 0;
            }

            switch (Type.GetTypeCode(type))
            {
                case TypeCode.Char:
                case TypeCode.Single:
                case TypeCode.Double:
                case TypeCode.Decimal:
                    return 1;
                case TypeCode.SByte:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                    return 2;
                case TypeCode.Byte:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64:
                    return 3;
                default:
                    return 0;
            }
        }

        #endregion

        #region GetIndexerProperty

        /// <summary>
        /// 返回类型的索引器
        /// </summary>
        /// <param name="type"></param>
        /// <param name="indexerArguments">索引参数类型</param>
        /// <returns></returns>
        public static PropertyInfo GetIndexerProperty(this Type type, params Type[] indexerArguments)
        {
            return (from p in type.GetProperties()
                    where AreArgumentsApplicable(indexerArguments, p.GetIndexParameters())
                    select p).FirstOrDefault();
        }

        private static bool AreArgumentsApplicable(IEnumerable<Type> arguments, IEnumerable<ParameterInfo> parameters)
        {
            var argumentList = arguments.ToList();
            var parameterList = parameters.ToList();

            if (argumentList.Count != parameterList.Count)
            {
                return false;
            }

            return !argumentList.Where((t, i) => parameterList[i].ParameterType != t).Any();
        }

        #endregion

        
    }
}